<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\behaviors;

use Closure;
use yii\base\Behavior;
use yii\base\Event;
use yii\db\ActiveRecord;

/**
 * 当某些事件发生时，AttributeBehavior会自动将指定值分配给ActiveRecord对象的一个或多个属性。
 *
 * 要使用AttributeBehavior，请配置[[attributes]]属性，该属性应指定需要更新的属性列表以及应触发更新的相应事件。
 * 然后使用PHP callable配置[[value]]属性，其返回值将用于分配给当前属性
 * For example,
 *
 * ```php
 * use yii\behaviors\AttributeBehavior;
 *
 * public function behaviors()
 * {
 *     return [
 *         [
 *             'class' => AttributeBehavior::className(),
 *             'attributes' => [
 *                 ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1',
 *                 ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
 *             ],
 *             'value' => function ($event) {
 *                 return 'some value';
 *             },
 *         ],
 *     ];
 * }
 * ```
 *
 * 由于属性值将由此行为自动设置，因此它们通常不是用户输入，因此不应进行验证，
 * 即它们不应出现在[[\ yii \ base \ Model :: rules（）| rules（）]]中。
 *
 */
class AttributeBehavior extends Behavior
{
    /**
     * @var array 要通过 [[value]] 指定的值自动填充的属性列表。 
     * 数组键是要 ActiveRecord 事件，并且数组值是要更新的相应属性。 
     * 您可以使用字符串表示单个属性，也可以使用数组表示属性列表。 例如，，
     * 
     * ```php
     * [
     *     ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'],
     *     ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
     * ]
     * ```
     */
    public $attributes = [];
    /**
     * @var mixed 将分配给当前属性的值。 
     * 这可以是一个匿名函数，数组格式调用（例如`[$ this，'methodName']`），
     * [[\ yii \ db \ Expression | Expression]] 对象的 DB 表达式（例如``new Expression（' NOW（）'）`），
     * 标量，字符串或任意值。 匿名函数格式如下
     * ```php
     * function ($event)
     * {
     *     // 返回值将被复制给对应属性
     * }
     * ```
     */
    public $value;
    /**
     * @var bool 是否在`$ owner`没有设置时跳过此行为
     * modified
     * @since 2.0.8
     */
    public $skipUpdateOnClean = true;
    /**
     * @var bool 是否保留非空属性值。
     * @since 2.0.13
     */
    public $preserveNonEmptyValues = false;


    /**
     * {@inheritdoc}
     */
    public function events()
    {
        return array_fill_keys(
            array_keys($this->attributes),
            'evaluateAttributes'
        );
    }

    /**
     * 计算属性值并将其分配给当前属性。
     * @param Event $event
     */
    public function evaluateAttributes($event)
    {
        if ($this->skipUpdateOnClean
            && $event->name == ActiveRecord::EVENT_BEFORE_UPDATE
            && empty($this->owner->dirtyAttributes)
        ) {
            return;
        }

        if (!empty($this->attributes[$event->name])) {
            $attributes = (array) $this->attributes[$event->name];
            $value = $this->getValue($event);
            foreach ($attributes as $attribute) {
                // 忽略非字符串的属性名称（例如，当由 TimestampBehavior :: updatedAtAttribute 时）
                if (is_string($attribute)) {
                    if ($this->preserveNonEmptyValues && !empty($this->owner->$attribute)) {
                        continue;
                    }
                    $this->owner->$attribute = $value;
                }
            }
        }
    }

    /**
     * 返回当前属性的值。 
     * 该方法由[[evaluateAttributes()]]调用。 
     * 其返回值将分配给与触发事件的属性。
     * 
     * @param Event $event 触发当前属性更新的事件。
     * @return mixed 属性值
     */
    protected function getValue($event)
    {
        if ($this->value instanceof Closure || (is_array($this->value) && is_callable($this->value))) {
            return call_user_func($this->value, $event);
        }

        return $this->value;
    }
}
